#!/usr/bin/env python
#
# Copyright 2009 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""OAuth authenticated HTTP fetches for appengine.

The appengine urlfetch API includes support for adding OAuth authentication
to requests, but the raw interface to that API involves setting special
HTTP headers on requests.  This module provides a friendlier interface,
roughly equivalent to the gadgets.io.makeRequest API described in
http://code.google.com/apis/gadgets/docs/oauth.html.
"""

from google.appengine.api import urlfetch

_POSSIBLE_FETCH_ARGS = frozenset([
    'oauth_access_token_url',
    'oauth_add_email',
    'oauth_authorization_url',
    'oauth_consumer_key',
    'oauth_consumer_secret',
    'oauth_desired_callback_url',
    'oauth_param_location',
    'oauth_received_callback_url',
    'oauth_request_method',
    'oauth_request_token',
    'oauth_request_token_secret',
    'oauth_request_token_url',
    'oauth_service_name',
    'oauth_sign_user',
    'oauth_state',
    'oauth_token_name',
    'oauth_use_token',
])


def make_fetch_call(rpc, url, payload=None, method=urlfetch.GET, headers=None,
                    allow_truncated=False, follow_redirects=True, **kwargs):
  """Asynchronously fetches the given URL using OAuth authentication.

  Most parameters match the urlfetch.fetch interface.  Extra parameters
  are roughly equivalent to the gadgets.io.makeRequest API described in
  http://code.google.com/apis/gadgets/docs/oauth.html.

  Args:
    rpc: urlfetch rpc request created with urlfetch.create_rpc.

    url: as for urlfetch.

    payload: as for urlfetch.

    method: as for urlfetch.

    headers: as for urlfetch.

    allow_truncated: as for urlfetch.

    follow_redirects: as for urlfetch.

    oauth_access_token_url: OAuth access token URL.

    oauth_add_email: 'true' if the user's e-mail should be added to outbound
        requests.

    oauth_authorization_url: OAuth authorization URL.

    oauth_consumer_key: consumer key to use to sign the request.  If not
        specified, a global key will be used instead.

    oauth_consumer_secret: consumer secret to use to sign the request.  If not
        specified, a global key will be used instead.

    oauth_desired_callback_url: callback URL to use if user permission is
        required to access data.  This should be the absolute URL of
        a callback handler on your application.

    oauth_param_location: where to pass OAuth parameters in the request.  By
        default the Authorization header is used.

    oauth_received_callback_url: if the user is accessing the callback handler
        of your application, pass the full URL of the request here.

    oauth_request_method: HTTP method for OAuth request token and access token
        requests.  Defaults to GET.

    oauth_request_token: preapproved OAuth request token.

    oauth_request_token_secret: preapproved OAuth request token secret.

    oauth_request_token_url: OAuth request token URL.

    oauth_service_name: service name used for consumer key and token storage.
        Consumer keys are stored under <your-application>/<service-name>.
        Tokens are stored under
        <your-application>/<user>/<service-name>/<token-name>.

    oauth_sign_user: 'true' if the user's global identifier should be included
        in outbound requests.

    oauth_state: state cookie from the user.  Used during the approval process
        to track the user.

    oauth_token_name: token name used for token storage.

    oauth_use_token: one of 'never', 'if_available', or 'always'.
        never: do not send an oauth token
        if_available: send an oauth token if one exists, otherwise send
            the request without a token.
        always: send a token if it exists, otherwise begin the process of
            requesting user approval.

  Raises:
    TypeError: an unknown parameter was passed.
  """
  if headers is None:
    headers = {}

  for arg, value in kwargs.items():
    if arg not in _POSSIBLE_FETCH_ARGS:
      raise TypeError(
          "%s() got an unexpected keyword argument '%s'" % (__name__, arg))
    headers['x_' + arg] = value

  urlfetch.make_fetch_call(rpc, url, payload, method, headers,
                           allow_truncated, follow_redirects)


_POSSIBLE_RESULT_ARGS = frozenset([
    'oauth_state',
    'oauth_approval_url',
    'oauth_error',
    'oauth_error_text',
])

class OAuthURLFetchResult(object):
  """URL fetch response that includes OAuth metadata."""

  def __init__(self, url_fetch_result):
    """Constructor.

    Args:
      url_fetch_result: result of the URL fetch

    Returns:
      Object with the following fields:
      url_fetch_result: the fetch result
      oauth_state: oauth_state value.  Return this on subsequent requests.
      oauth_approval_url: URL to redirect the user to for approval
      oauth_error: machine readable error code
      oauth_error_text: detailed text describing the error
    """
    self.url_fetch_result = url_fetch_result
    for attr in _POSSIBLE_RESULT_ARGS:
      name = 'x_' + attr
      if name in url_fetch_result.headers:
        self.__setattr__(attr, url_fetch_result.headers[name])
        del url_fetch_result.headers[name]
      else:
        self.__setattr__(attr, None)

  def __str__(self):
    data = {
        'url_fetch_result.content': self.url_fetch_result.content,
        'url_fetch_result.status_code': self.url_fetch_result.status_code,
        'url_fetch_result.content_was_truncated':
        self.url_fetch_result.content_was_truncated,
        'url_fetch_result.headers': self.url_fetch_result.headers,
        'oauth_approval_url': self.oauth_approval_url,
        'oauth_state': self.oauth_state,
        'oauth_error': self.oauth_error,
        'oauth_error_text': self.oauth_error_text,
    }
    return data.__str__()
